﻿using System.ComponentModel;
using System.Text.RegularExpressions;
using Dyao.Book.Application;
using Dyao.Book.Application.Abstractions;
using Dyao.Book.Contract;
using Dyao.Book.Entity;
using Dyao.Book.Repository.Abstractions;
using Foundation.Core;
using Foundation.IdCreate;
using Foundation.Spider;
using Foundation.Spider.DataFlow;
using Foundation.Spider.DataFlow.Parser;
using Foundation.Spider.Http;
using Foundation.Spider.Selector;
using Foundation.Uow;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Dyao.Book.ApplicationSpiders
{
    /// <summary>
    /// 起点书目录爬取
    /// </summary>
    [DisplayName("QiDianBookCatalogue")]
    public class QiDianBookCatalogueSpider : Spider, IQiDianBookCatalogueSpider
    {
        private readonly ISpiderManager _spiderMgr;
        public QiDianBookCatalogueSpider(IOptions<SpiderOptions> options,
            DependenceServices services,
            ILogger<QiDianBookCatalogueSpider> logger,
            ISpiderManager spiderMgr) : base(options, services, logger)
        {
            _spiderMgr = spiderMgr;
        }

        /// <inheritdoc cref="IQiDianBookCatalogueSpider.CrawCatalogueByBookAsync"/>
        public async Task<bool> CrawCatalogueByBookAsync(CrawCatalogueDto dto, CancellationToken cancellationToken)
        {
            var request = BuildRequest(dto.SpiderUrl);
            request.Properties.Add(QiDianConstant.CatalogueRequestBookProp, dto);
            await ExecuteAsync(cancellationToken, async () =>
            {
                await AddRequestsAsync(request);
            });

            return true;
        }

        protected override async Task InitializeAsync(CancellationToken stoppingToken = default)
        {
            // 添加自定义解析
            AddDataFlow<QiDianCatalogueDataParser>();
            AddDataFlow<ConsoleStorage>();

            // 错误信息
            OnRequestError += (request, response) =>
            {
                var bookProp = (CrawCatalogueDto)request.Properties.GetOrDefault(QiDianConstant.CatalogueRequestBookProp);
                string message = $"Uri:{request.RequestUri}, ReasonPhrase:{response.ReasonPhrase}";
                _spiderMgr.PushMessageAsync(bookProp.BookId, OperationTypeEnum.Chapter, message);
            };

            // 超时请求
            OnRequestTimeout += (requests) =>
            {
                foreach (var request in requests)
                {
                    var bookProp = (CrawCatalogueDto)request.Properties.GetOrDefault(QiDianConstant.CatalogueRequestBookProp);
                    string message = $"Uri:{request.RequestUri} RequestTimeout";
                    _spiderMgr.PushMessageAsync(bookProp.BookId, OperationTypeEnum.Chapter, message);
                }
            };
            await Task.CompletedTask;
        }

        /// <summary>
        /// 构建请求
        /// </summary>
        /// <param name="page">页码</param>
        /// <returns></returns>
        public Request BuildRequest(string spiderUrl)
        {
            var request = new Request(spiderUrl)
            {
                // 请求超时10秒
                Timeout = 10000
            };
            request.Headers.Cookie = QiDianConstant.UserCookie;
            request.Headers.UserAgent = QiDianConstant.UserAgent;

            return request;
        }
    }

    public class QiDianCatalogueDataParser : DataParser
    {
        public override Task InitializeAsync()
        {
            return Task.CompletedTask;
        }

        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="context">上下文</param>
        /// <returns></returns>
        protected override async Task ParseAsync(DataFlowContext context)
        {
            var bookProp = (CrawCatalogueDto)context.Request.Properties.GetOrDefault(QiDianConstant.CatalogueRequestBookProp);
            var logger = context.ServiceProvider.GetService<ILogger<QiDianCatalogueDataParser>>();
            var spiderMgr = context.ServiceProvider.GetService<ISpiderManager>();
            var bookCatalogues = new List<BookCatalogueEntity>();
            var idCreate = context.ServiceProvider.GetService<IIdCreater>();
            try
            {
                // 网页数据解析
                var masterNode = context.Selectable.Select(Selectors.XPath(".//div[@class='catalog-all']"));
                var volumeNodes = masterNode.SelectList(Selectors.XPath(".//div[@class='catalog-volume']")); // 卷目录
                string wordsNumPattern = @"章节字数：(\d+)"; // 字数匹配

                // 卷
                foreach (var volume in volumeNodes)
                {
                    var volumeId = volume.Select(Selectors.XPath(".//label/@for")).Value;
                    var volumeName = volume.Select(Selectors.XPath(".//label/div/h3")).Value;
                    bool isFree = volume.Select(Selectors.XPath(".//label/div/h3/span[@class='free']")) != null;
                    // 章节
                    var chapterNodes = volume.SelectList(Selectors.XPath(".//li[@class='chapter-item']"));

                    var rid = 0;
                    foreach (var chapter in chapterNodes)
                    {
                        rid++;
                        var chapterName = chapter.Select(Selectors.XPath(".//a")).Value;
                        var link = chapter.Select(Selectors.XPath(".//a/@href")).Value;
                        var title = chapter.Select(Selectors.XPath(".//a/@title")).Value;

                        // 是否有锁
                        var isLock = chapter.Select(Selectors.XPath(".//em")) != null;

                        int wordsNum = 0;
                        Match match = Regex.Match(title, wordsNumPattern);
                        if (match.Success)
                            wordsNum = Convert.ToInt32(match.Groups[1].Value);

                        var code = await idCreate.CreateAsync(IdBusinessType.BookChapterRedisId, null, default);
                        bookCatalogues.Add(new BookCatalogueEntity()
                        {
                            ID = code.ToString(),
                            BookId = bookProp.BookId,
                            IsFree = isFree || !isLock,
                            IsMatchSource = isFree || !isLock,
                            CrawlComplete = false,
                            VolumeId = volumeId,
                            VolumeName = volumeName.SplitDefault("·").FirstOrDefault() ?? "默认卷",
                            CatalogueSeq = rid,
                            CatalogueName = chapterName,
                            ContentLink = link,
                            Keyword = chapterName.SplitDefault(" ").JoinAsString(","),
                            WordsNumber = wordsNum,
                            SourceKey = BookSourceType.QiDian.ToString(),
                        });
                    }
                }
                // 插入
                await BulkAsync(context, bookCatalogues);
            }
            catch (Exception ex)
            {
                logger.LogError(ex, ex.Message);
                await spiderMgr.PushMessageAsync(bookProp.BookId, OperationTypeEnum.Chapter, $"Parse Error:{ex.GetTraceMessage()}");
            }

            await Task.CompletedTask;
        }

        public async Task BulkAsync(DataFlowContext context, List<BookCatalogueEntity> bookCatalogues)
        {
            CancellationToken cancellationToken = default;
            var bookProp = (CrawCatalogueDto)context.Request.Properties.GetOrDefault(QiDianConstant.CatalogueRequestBookProp);
            var uowMgr = context.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
            var logger = context.ServiceProvider.GetRequiredService<ILogger<QiDianCatalogueDataParser>>();
            var bookCatalogueRepo = context.ServiceProvider.GetRequiredService<IBookCatalogueRepository>();
            var chapterSourceRepo = context.ServiceProvider.GetRequiredService<IBookCatalogueMatchSourceRepository>();
            var commonMgr = context.ServiceProvider.GetRequiredService<ICommonManager>();

            logger.LogInformation($"开始添加，数量{bookCatalogues.Count()}");

            using (var uow = uowMgr.Begin(isReadDb: true, requiresNew: true))
            {
                var titles = bookCatalogues.Select(b => b.CatalogueName).ToList();
                var chapterNames = await bookCatalogueRepo.GetListByBookIdAsync(bookProp.BookId, b => new BookChapterNameDto()
                {
                    BookId = b.BookId,
                    ChapterName = b.CatalogueName,
                }, cancellationToken);
                bookCatalogues.RemoveAll(b => chapterNames.Any(e => e.ChapterName.Equals(b.CatalogueName)));
            }

            logger.LogInformation($"过滤已有数据，数量{bookCatalogues.Count()}");
            if (!bookCatalogues.Any()) return;

            var chapterLogs = bookCatalogues.Select(b => new OperationLogDto()
            {
                OperationLog = $"添加章节:{b.CatalogueName}",
                OperationType = OperationTypeEnum.Chapter,
                SourceId = b.ID
            });

            // 免费章节源
            var freeChapterSources = bookCatalogues.Where(b => b.IsFree).Select(b => new BookCatalogueMatchSourceEntity()
            {
                CreateDateTime = DateTime.Now,
                SourceKey = b.SourceKey,
                SourceUrl = b.ContentLink,
                BookId = b.BookId,
                BookCatalogueId = b.ID
            });

            using (var uow = uowMgr.Begin(isTransactional: true))
            {
                try
                {
                    if (freeChapterSources.Any())
                        await chapterSourceRepo.BulkInsertAsync(freeChapterSources, cancellationToken);
                    await bookCatalogueRepo.BulkInsertAsync(bookCatalogues, cancellationToken);
                    await commonMgr.AddOperationLogsAsync(chapterLogs.ToArray());
                    await uow.CompleteAsync();
                    logger.LogInformation($"添加成功，数量{bookCatalogues.Count()}");
                }
                catch (Exception ex)
                {
                    logger.LogError($"添加失败，{ex.Message}", ex);
                    await uow.RollbackAsync();
                    throw;
                }
            }
        }
    }
}
